package controls.web.implementation;

import org.openqa.selenium.By;
import org.openqa.selenium.By.ByClassName;
import org.openqa.selenium.By.ByCssSelector;
import org.openqa.selenium.By.ById;
import org.openqa.selenium.By.ByLinkText;
import org.openqa.selenium.By.ByName;
import org.openqa.selenium.By.ByPartialLinkText;
import org.openqa.selenium.By.ByTagName;
import org.openqa.selenium.By.ByXPath;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import reporting.Report;

import com.gargoylesoftware.htmlunit.ElementNotFoundException;

import core.PropertiesManager;
import core.WebTestContext;
import enums.SearchBy;

public class Control implements controls.web.interfaces.Control {

	protected String descriptiveName;

	private WebElement webElement;

	public Control(String descriptiveName, By... criterion) {
		WebDriver webDriver = WebTestContext.getWebDriver();

		// Check whether web driver is initialized
		if (webDriver != null) {
			try {
				// Set descriptive name
				this.descriptiveName = descriptiveName;

				// Try to initialize the web element for the time defined in
				// find.timeout.sec
				long startTime = System.nanoTime();

				WebDriverWait wait = new WebDriverWait(webDriver,
						PropertiesManager.getInt("find.timeout.sec"));
				webElement = wait
						.until(ExpectedConditions
								.presenceOfElementLocated(prepareSerachStatement(criterion)));

				long elapsedTime = System.nanoTime() - startTime;

				System.out.println("Elapsed(ms): " + elapsedTime / 1000000);

			} catch (Exception ex) {
				Report.error(this.descriptiveName + " is NOT initialised");
			}
		} else {
			Report.error("WebDriver is NOT initialised. Please verify that your test class extends BaseTestCase.");
		}
	}

	private By prepareSerachStatement(By... criterion) throws Exception {
		if (criterion == null) {
			return null;
		}

		if (criterion.length == 1) {
			// There is only one search attribute
			return criterion[0];

		} else {
			// There are more then one search attributes

			return convertToXPath(criterion);

		}
	}

	private By convertToXPath(By... criterion) throws Exception {
		By xpath = null;
		String xpathExpression = "";

		for (By criteria : criterion) {
			SearchBy type = getCriterionType(criteria);

			switch (type) {
			case id: {
				String value = getCriterionValue(criteria);
				xpathExpression += "//*[@id='" + value + "'] | ";
			}
				break;

			case name: {
				String value = getCriterionValue(criteria);
				xpathExpression += "//*[@name='" + value + "'] | ";
			}
				break;

			case className: {
				String value = getCriterionValue(criteria);
				xpathExpression += "//*[@class='" + value + "'] | ";
			}
				break;

			case xpath: {
				xpathExpression += getCriterionValue(criteria) + " | ";
			}
				break;

			default: {
				throw new Exception("Criterion can not be converted to XPath");
			}
			}
		}

		if (xpathExpression.length() == 0) {
			throw new Exception("Criterion can not be converted to XPath");
		} else {
			xpath = By.xpath(xpathExpression.substring(0,
					xpathExpression.lastIndexOf('|')));
		}

		return xpath;
	}

	private String getCriterionValue(By criterion) {
		String value = null;

		try {
			value = criterion.toString();
			value = value.substring(value.indexOf(":") + 1);
			value = value.trim();
		} catch (IndexOutOfBoundsException ex) {

		}

		return value;
	}

	private SearchBy getCriterionType(By criterion) {
		SearchBy type = null;
		if (criterion instanceof ById) {
			type = SearchBy.id;
		} else if (criterion instanceof ByClassName) {
			type = SearchBy.className;
		} else if (criterion instanceof ByLinkText) {
			type = SearchBy.linkText;
		} else if (criterion instanceof ByName) {
			type = SearchBy.name;
		} else if (criterion instanceof ByPartialLinkText) {
			type = SearchBy.partialLinkText;
		} else if (criterion instanceof ByTagName) {
			type = SearchBy.tagName;
		} else if (criterion instanceof ByXPath) {
			type = SearchBy.xpath;
		} else if (criterion instanceof ByCssSelector) {
			type = SearchBy.cssSelector;
		}
		return type;
	}

	@Override
	public void click() {
		if (webElement != null) {
			try {
				webElement.click();
				Report.pass(this.descriptiveName + " is clicked");
			} catch (Exception ex) {
				Report.error(this.descriptiveName + " is NOT clicked", ex);
			}
		} else {
			Report.error(this.descriptiveName + " is NOT initialised");
		}

	}

	@Override
	public boolean isEnabled() {
		boolean isEnabled = false;
		if (webElement != null) {
			try {
				isEnabled = webElement.isEnabled();
			} catch (Exception ex) {
				Report.error(this.descriptiveName
						+ " status CAN NOT be determined", ex);
			}
			if (isEnabled) {
				Report.info(this.descriptiveName + " is enabled");
			} else {
				Report.info(this.descriptiveName + " is disabled");
			}
		} else {
			Report.error(this.descriptiveName + " is NOT initialised");
		}
		return isEnabled;
	}

	@Override
	public void isEnabled(boolean isEnabled) {
		if (webElement != null) {
			try {
				if (isEnabled == webElement.isEnabled()) {
					Report.pass(this.descriptiveName + " is "
							+ (isEnabled ? "enabled" : "disabled")
							+ " as expected");
				} else {
					Report.error(this.descriptiveName + " is NOT "
							+ (isEnabled ? "enabled" : "disabled")
							+ " as it should be");
				}
			} catch (Exception ex) {
				Report.error(this.descriptiveName
						+ " status CAN NOT be determined", ex);
			}
		} else {
			Report.error(this.descriptiveName + " is NOT initialised");
		}
	}

	@Override
	public boolean isVisible() {
		boolean isVisible = false;
		if (webElement != null) {
			try {
				isVisible = webElement.isDisplayed();
			} catch (Exception ex) {
				Report.error(this.descriptiveName
						+ " status CAN NOT be determined", ex);
			}
			if (isVisible) {
				Report.info(this.descriptiveName + " is visible");
			} else {
				Report.info(this.descriptiveName + " is invisible");
			}

		} else {
			Report.error(this.descriptiveName + " is NOT initialised");
		}
		return isVisible;
	}

	@Override
	public void isVisible(boolean isVisible) {
		if (webElement != null) {
			try {
				if(isVisible == webElement.isDisplayed())
				{
					Report.pass(this.descriptiveName + " is "
							+ (isVisible ? "visible" : "invisible")
							+ " as expected");
				}
				else
				{
					Report.error(this.descriptiveName + " is NOT "
							+ (isVisible ? "visible" : "invisible")
							+ " as it should be");
				}
			} catch (Exception ex) {
				Report.error(this.descriptiveName
						+ " status CAN NOT be determined", ex);
			}

		} else {
			Report.error(this.descriptiveName + " is NOT initialised");
		}
	}

	public String getDescriptiveName() {
		return descriptiveName;
	}

	protected WebElement getWebElement() {
		if (webElement == null) {
			throw new ElementNotFoundException(descriptiveName, "", "");
		}
		return webElement;
	}

}
